package com.trinhrae.shooting.moduleAuth.service;

import com.alibaba.fastjson.JSONObject;
import com.trinhrae.shooting.entity.BaseResponse;
import com.trinhrae.shooting.entity.MailRequest;
import com.trinhrae.shooting.exception.BusinessException;
import com.trinhrae.shooting.moduleGeneral.service.EmailService;
import com.trinhrae.shooting.moduleGeneral.service.KafkaProducerService;
import com.trinhrae.shooting.util.RedisUtil;
import com.trinhrae.shooting.util.VerifyUtil;
import freemarker.template.TemplateException;
import org.springframework.stereotype.Component;

import javax.mail.MessagingException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Set;

/**
 * 注册验证码服务
 *
 * @Author: TrinhRae
 * @Package: com.trinhrae.shooting.moduleAuth.service
 * @Project: shooting
 * @Date: 2025/9/2 16:28
 */
@Component
public class RegisterVerCodeService {
    private final VerifyUtil verifyUtil;
    private final RedisUtil redisUtil;
    private final EmailService emailService;
    private final KafkaProducerService kafkaProducerService;
    private final static String CODE = "REGISTER_CODE:CODE:";
    private final static String CODE_VER = "REGISTER_CODE:VERSION:";
    private final static String CODE_COUNT = "REGISTER_CODE:COUNT:";
    private final static int CODE_SAVE_T = 420;
    private final static int CODE_LIMIT_T = 60;
    private final static int CODE_EXPIRED_T = 300000;
    private final static int CODE_VER_SAVE_T = 420;
    private final static int CODE_COUNT_SAVE_T = 1800;

    public RegisterVerCodeService(VerifyUtil verifyUtil, RedisUtil redisUtil, EmailService emailService, KafkaProducerService kafkaProducerService) {
        this.verifyUtil = verifyUtil;
        this.redisUtil = redisUtil;
        this.emailService = emailService;
        this.kafkaProducerService = kafkaProducerService;
    }

    /**
     * 下发验证码至邮箱（生成验证码，存储验证码，下发验证码）
     *
     * @param email 邮箱
     * @return 受理结果响应
     */
    public BaseResponse sendCodeToEmail(String email) throws MessagingException, TemplateException, IOException {
        // 验证是否可以下发验证码
        judgeSendCode(email);

        // 生成时间戳
        long timeStamp = System.currentTimeMillis();

        // 让上一个验证码失效（如果有）
        if (redisUtil.hasKey(CODE_VER + email)) {
            long version = (long) redisUtil.getValue(CODE_VER + email);
            redisUtil.setHash(CODE + email + ":" + version, "status", "INVALID");
        }

        // 生成一个验证码hash
        String code = verifyUtil.generateCode();
        HashMap<String, Object> map = new HashMap<>();
        map.put("code", code);
        map.put("status", "VALID");
        map.put("expireAt", timeStamp + CODE_EXPIRED_T);

        // 存验证码、批次号、计数器+1
        redisUtil.saveHashAll(CODE + email + ":" + timeStamp, map, CODE_SAVE_T);
        redisUtil.saveString(CODE_VER + email, timeStamp, CODE_VER_SAVE_T);
        redisUtil.incr5TimesWithExpire(CODE_COUNT + email, CODE_COUNT_SAVE_T);

        JSONObject infoContent = new JSONObject();
        infoContent.put("email", email);
        infoContent.put("code", code);
        kafkaProducerService.sendVerifyCode("send-verify-code", infoContent.toString());
        return BaseResponse.success("发送成功");
    }

    /**
     * 判断当前邮箱是否可以下发验证码
     *
     * @param email 邮箱
     */
    private void judgeSendCode(String email) {
        String codeKey = CODE_VER + email;
        String countKey = CODE_COUNT + email;
        long ttl = redisUtil.getExpire(codeKey);
        int count = redisUtil.hasKey(countKey)
                ? (int) redisUtil.getValue(countKey)
                : 0;
        if (ttl > CODE_SAVE_T - CODE_LIMIT_T) {
            throw new BusinessException(10001, "1分钟内不能重复发送验证码！");
        } else if (count > 4) {
            throw new BusinessException(10002, "已在30分钟内发送过5次，30分钟后重试");
        }
    }

    /**
     * 校验验证码
     *
     * @param codeInfo 验证码信息（邮箱信息account，验证码verCode）
     * @return 校验结果
     */
    public BaseResponse checkCode(JSONObject codeInfo) {
        String account = codeInfo.getString("account");
        String verCode = codeInfo.getString("verCode");

        // 判断是否有最新验证码
        if (!redisUtil.hasKey(CODE_VER + account)) {
            return BaseResponse.error(400, "验证码错误");
        }

        // 读取最新版进行验证
        long version = (long) redisUtil.getValue(CODE_VER + account);
        HashMap codeMap = (HashMap) redisUtil.getEntireHash(CODE + account + ":" + version);
        if (codeMap == null || codeMap.isEmpty()) {
            return BaseResponse.error(400, "验证码错误");
        }

        // 最新版验证码校验
        String code = (String) codeMap.get("code");
        String status = (String) codeMap.get("status");
        long expireAt = (long) codeMap.get("expireAt");
        if (verCode.equals(code) && status.equals("VALID") && System.currentTimeMillis() < expireAt) {
            redisUtil.setHash(CODE + account + ":" + version, "status", "INVALID");
            return BaseResponse.success("验证成功！");
        }

        // 更改过期验证码状态
        if (verCode.equals(code) && status.equals("VALID") && System.currentTimeMillis() > expireAt) {
            redisUtil.setHash(CODE + account + ":" + version, "status", "EXPIRED");
            return BaseResponse.success("验证码已过期！");
        }

        // 查看验证码状态
        Set<String> keys = redisUtil.scanKeys(CODE + account + "*");
        for (String key : keys) {
            String value = (String) redisUtil.getHash(key, "code");
            if (value.equals(verCode)) {
                switch ((String) redisUtil.getHash(key, "status")) {
                    case "INVALID":
                        return BaseResponse.error(500, "验证码已失效");
                    case "EXPIRED":
                        return BaseResponse.error(500, "验证码已过期");
                    case "VALID":
                        redisUtil.setHash(CODE + account + ":" + version, "status", "EXPIRED");
                        return BaseResponse.error(500, "验证码已过期");
                }
            }
        }
        return BaseResponse.error(500, "验证码错误");
    }
}
